package com.iob.coin.covid19kg.kg;

import com.iob.coin.covid19kg.dataset.DatasetManager;
import com.iob.coin.covid19kg.dataset.DatasetManagerLocalImpl;
import com.iob.coin.covid19kg.dataset.DefaultURI;
import com.iob.coin.covid19kg.exceptions.AnswerException;
import com.iob.coin.entity.KG;
import com.iob.coin.entity.Node;
import com.iob.coin.entity.Triple;
import org.apache.jena.rdf.model.*;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.RDFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.Timestamp;
import java.util.LinkedList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 * @Author: GuoXinyuan
 * @Date: 2021/6/9
 * 用于构建图谱
 */
@Component
public class KGBuilder {

    @Autowired
    DatasetManager datasetManager = new DatasetManagerLocalImpl();

    /**
     * 延伸resource直接相关关系
     *  会向给定set中填充对应关系
     * @param resource
     * @param kg
     */
    public void buildResourceRelation(Resource resource, KG kg){
        //作为图谱源节点
        Node resourceNode = transformResource2Node(resource);//为减少数据库访问 标签赋予在一次批量查询后完成

        Node resourceTypeNode = null;

        //获取资源作为主语的所有相关关系
        List<Statement> statements = datasetManager.searchAboutResource(resource);
        for (Statement statement : statements) {
            Property property = statement.getPredicate();
            //label关系
            if (property.equals(RDFS.label)) {
            }
            //type关系
            else if (property.equals(RDF.type)) {
                Resource type = statement.getObject().asResource(); //断言是Resource
                //type作为类型添加
                resourceTypeNode = transformResource2Node(type);
                kg.addType(resourceTypeNode);
                //type作为节点添加
                Node typeOftype = getNodeTypeNode(type);
                resourceTypeNode.setType(typeOftype);
                kg.addType(typeOftype);
                kg.addNode(resourceTypeNode);
                //将源节点添加至节点集
                resourceNode.setType(resourceTypeNode);
                kg.addNode(resourceNode);
                //添加type关系
                Triple triple = transformProperty2Triple(
                        RDF.type,resourceNode,resourceTypeNode,"type","type");
                kg.addTriple(triple);
            }
            //其他关系
            else {
                RDFNode object = statement.getObject();
                Node objectNode = null;
                if(object.isLiteral()){
                    objectNode = transformLiteral2Node(object.asLiteral());
                    kg.addNode(objectNode);
                }
                else if(object.isResource()){
                    Resource objectResource = object.asResource();
                    objectNode = transformResource2Node(objectResource);
                    Node typeNode = getNodeTypeNode(objectResource);
                    objectNode.setType(typeNode);
                    kg.addType(typeNode);
                    kg.addNode(objectNode);
                }
                if(objectNode!=null){
                    Triple triple = transformProperty2Triple(property, resourceNode, objectNode);
                    kg.addTriple(triple);
                }
            }
        }

    }

    /**
     * 延伸出type相关关系（type、domain、range关系）
     * @param classResource 要求是RDFS.Class
     * @param kg 需要补充的kg
     */
    public void buildTypeRelation(Resource classResource,KG kg) throws AnswerException {
        if(!RDFS.Class.equals(datasetManager.getRDFBaseType(classResource))) throw new AnswerException();

        //扩展单个函数domain、range等关系逻辑封装
        Consumer<Resource> extend = typeResource -> {
            Node typeResourceNode = transformResource2Node(typeResource);
            List<Statement> statements = new LinkedList<>();
            statements.addAll(datasetManager.listStatements(null,RDFS.domain,typeResource));
            statements.addAll(datasetManager.listStatements(null,RDFS.range,typeResource));
            for(Statement statement:statements){
                Resource subject = statement.getSubject();
                Property property = statement.getPredicate();
                //创建节点关系
                Node propertyNode = transformResource2Node(subject);
                propertyNode.setType(KG.getBaseTypeByUri(RDF.Property.getURI())); //一定是Property
                String label = RDFS.domain.equals(property)?"domain":"range";
                kg.addNode(propertyNode);
                //创建三元组
                Triple triple1 = transformProperty2Triple(
                        property, propertyNode, typeResourceNode,label,label);
                kg.addTriple(triple1);
                //查询property domain、range的另一端
                Property otherSideProperty = RDFS.domain.equals(property)?RDFS.range:RDFS.domain;
                String otherSideLabel = RDFS.domain.equals(property)?"range":"domain";
                Statement otherSideStatement = datasetManager.getProperty(subject,otherSideProperty);
                RDFNode object = otherSideStatement.getObject();
                if(object.isResource()){
                    Resource resource = object.asResource();
                    //添加类型
                    Node anotherSideType = getNodeTypeNode(resource);
                    kg.addType(anotherSideType);
                    //添加节点
                    Node anotherSideNode = transformResource2Node(resource);
                    anotherSideNode.setType(anotherSideType);
                    kg.addNode(anotherSideNode);
                    //添加三元组
                    Triple triple2 = transformProperty2Triple(
                            otherSideProperty, propertyNode, anotherSideNode,otherSideLabel,otherSideLabel);
                    kg.addTriple(triple2);
                }
            }

        };

        //生成type类型
        Node classResourceTypeNode = getNodeTypeNode(classResource);
        kg.addType(classResourceTypeNode);
        //生type节点
        Node classResourceNode = transformResource2Node(classResource);
        classResourceNode.setType(classResourceTypeNode);
        kg.addNode(classResourceNode);
        //延伸domain、range关系
        extend.accept(classResource);

        //延伸父节点关系
        {
            Node sonNode = classResourceNode;
            Statement statement = datasetManager.getProperty(classResource,RDFS.subClassOf);
            while (statement!=null){
                Resource superResource = statement.getObject().asResource();
                //生成superResource type类型
                Node superTypeNode = getNodeTypeNode(superResource);
                kg.addType(superTypeNode);
                //生成superResource node节点
                Node superNode = transformResource2Node(superResource);
                superNode.setType(superTypeNode);
                kg.addNode(superNode);
                //添加triple关系
                Triple classResourceTriple = transformProperty2Triple(
                        RDFS.subClassOf,sonNode, superNode,"subClassOf","subClassOf");
                kg.addTriple(classResourceTriple);
                //延伸domain、range关系
                extend.accept(superResource);
                //下一次循环设置
                statement = datasetManager.getProperty(superResource,RDFS.subClassOf);
                sonNode = superNode;
            }
        }

        //递归延伸子节点关系
        (new Consumer<Resource>() {
            @Override
            public void accept(Resource superType) {
                Node superNode = transformResource2Node(superType);
                List<Statement> statements = datasetManager.listStatements(null,RDFS.subClassOf,superType);
                for(Statement statement:statements){
                    Resource sonResource = statement.getSubject();
                    //生成sonResource类型
                    Node sonTypeNode = getNodeTypeNode(sonResource);
                    kg.addType(sonTypeNode);
                    //生成sonResource node节点
                    Node sonNode = transformResource2Node(sonResource);
                    sonNode.setType(sonTypeNode);
                    kg.addNode(sonNode);
                    //生成三元组
                    Triple typeTriple = transformProperty2Triple(
                            RDFS.subClassOf,sonNode, superNode,"subClassOf","subClassOf");
                    kg.addTriple(typeTriple);
                    //延伸domain、range关系
                    extend.accept(sonResource);
                    //递归
                    this.accept(sonResource);
                }
            }
        }).accept(classResource);
    }

    /**
     * 延伸resource的类型相关关系
     * @param resource
     * @param kg
     */
    public void buildTypeOfResourceRelation(Resource resource, KG kg) throws AnswerException {
        Statement statement = datasetManager.getProperty(resource,RDF.type);
        if(statement == null) return;
        Resource typeResource = statement.getObject().asResource();

        buildTypeRelation(typeResource,kg);
    }

    /**
     * 将resource封装为一个Node
     * @param resource
     * @return
     */
    public Node transformResource2Node(Resource resource){
        Node node = KG.getBaseTypeByUri(resource.getURI());
        if(node != null) return node;
        List<Statement> labels = datasetManager.listStatements(resource,RDFS.label,null);
        String labelZh = null;
        String labelEn = null;
        for (Statement labelStatement:labels) {
            Literal literal = labelStatement.getObject().asLiteral();
            if(literal.getLanguage().equals("zh"))
                labelZh = literal.getString();
            else if(literal.getLanguage().equals("en"))
                labelEn = literal.getString();
        }
        return transformResource2Node(resource,labelZh,labelEn);
    }
    public Node transformResource2Node(Resource resource,String labelZh,String labelEn){
        Node node = KG.getBaseTypeByUri(resource.getURI());
        if(node != null) return node;
        node = new Node();
        node.setUri(resource.getURI());
        node.setLabel_ZH(labelZh);
        node.setLabel_EN(labelEn);
//        Node type = getNodeTypeNode(resource);
//        node.setType(type);
        return node;
    }
    public Node transformLiteral2Node(Literal literal){
        Node node = new Node();
        node.setUri(String.valueOf(literal.getString().hashCode()));
        node.setLabel_ZH(literal.getString());
        node.setType(KG.getBaseTypeByUri(DefaultURI.xmlString));
        return node;
    }

    /**
     * 获取类型节点 节点类型的类型为null（因为此方法返回值默认会被放入KG.types 类型信息是无用的）
     * @return
     */
    public Node getNodeTypeNode(Resource resource){
        //resource是基础类型 返回基本类型
        Node node = KG.getBaseTypeByUri(resource.getURI());
        if(node!=null) return KG.getBaseTypeByUri(DefaultURI.baseElement);
        Statement statement = datasetManager.getProperty(resource,RDF.type);
        Resource type = statement.getObject().asResource();
        String labelZh = null;
        String labelEn = null;
        List<Statement> labels = datasetManager.listStatements(type,RDFS.label,null);
        for (Statement labelStatement:labels) {
            Literal literal = labelStatement.getObject().asLiteral();
            if(literal.getLanguage().equals("zh"))
                labelZh = literal.getString();
            else if(literal.getLanguage().equals("en"))
                labelEn = literal.getString();
        }
        node = new Node();
        node.setUri(type.getURI());
        node.setLabel_ZH(labelZh);
        node.setLabel_EN(labelEn);
        return node;
    }

    /**
     * 将Property转化为Triple
     * @return
     */
    public Triple transformProperty2Triple(Property property,Node source,Node target) {
        String labelZh = datasetManager.getLabel(property,"zh");
        String labelEn = datasetManager.getLabel(property,"en");
        if(labelZh==null&&labelEn==null){
            labelZh = labelEn = property.getLocalName();
        }
        return transformProperty2Triple(property,source,target,labelZh,labelEn);
    }
    public Triple transformProperty2Triple(
            Property property,Node source,Node target, String labelZh,String labelEn) {
        Triple triple = new Triple();
        triple.setUri(property.getURI());
        triple.setLabel_ZH(labelZh);
        triple.setLabel_EN(labelEn);
        triple.setSource(source);
        triple.setTarget(target);
        return triple;
    }




}
